﻿using System;
using System.Globalization;

namespace CoreRay
{
    //-----------------------------------------------------------------------------------------
    /// <summary>
    /// Structure that represents a 3D vector.
    /// </summary>
    //-----------------------------------------------------------------------------------------
    [Serializable]
    public struct Vector3 : IEquatable<Vector3>
	{
        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Vector with a magnitude of zero.
        /// </summary>
        //-----------------------------------------------------------------------------------------

        public static readonly Vector3 ZeroVector = new Vector3(0, 0, 0);


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Initializes a new instance of the <see cref="Vector3"/> struct.
        /// </summary>
        /// <param name="x">The X component of the vector.</param>
        /// <param name="y">The y component of the vector.</param>
        /// <param name="z">The z component of the vector.</param>
        //-----------------------------------------------------------------------------------------

        public Vector3(float x, float y, float z) : this()
		{
			this.X = x;
			this.Y = y;
			this.Z = z;
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the X component of the vector.
        /// </summary>
        /// <value>The X component of the vector.</value>
        //-----------------------------------------------------------------------------------------

        public float X
        {
            get;
            private set;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the Y component of the vector.
        /// </summary>
        /// <value>The Y component of the vector.</value>
        //-----------------------------------------------------------------------------------------

        public float Y
        {
            get;
            private set;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the Z component of the vector.
        /// </summary>
        /// <value>The Z component of the vector.</value>
        //-----------------------------------------------------------------------------------------

        public float Z
        {
            get;
            private set;
        }

        public float this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0:
                        return this.X;

                    case 1:
                        return this.Y;

                    case 2:
                        return this.Z;

                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the magnitude of the vector.
        /// </summary>
        /// <value>The magnitude of the vector.</value>
        //-----------------------------------------------------------------------------------------

        public float Magnitude
		{
			get
			{
				return (float) System.Math.Sqrt(this.X * this.X + this.Y * this.Y + this.Z * this.Z);
			}
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the magnitude of the vector squared.
        /// </summary>
        /// <remarks>This property can be usefull in performance critical sections.</remarks>
        /// <value>The squared magnitude of the vector.</value>
        //-----------------------------------------------------------------------------------------

        public float MagnitudeSquared
        {
            get
            {
                return this.X * this.X + this.Y * this.Y + this.Z * this.Z;
            }
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets a value indicating whether this vector is normalized.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is normalized; otherwise, <c>false</c>.
        /// </value>
        //-----------------------------------------------------------------------------------------

        public bool IsNormalized
        {
            get
            {
                float magnitude = this.Magnitude;

                if(magnitude > 0.99999f && magnitude < 1.00001f)
                {
                    return true;
                }

                return false;
            }
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Normalizes the vector.
        /// </summary>
        /// <returns>Normalized copy of the vector.</returns>
        //-----------------------------------------------------------------------------------------

        public Vector3 Normalize()
		{
			float magnitude = this.Magnitude;

			if(magnitude != 0.0f)
			{
				float temp = 1.0f / magnitude;

				return new Vector3(this.X * temp, this.Y * temp, this.Z * temp);
			}

			else
			{
				return this;
			}
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Performs a cross product between two vectors.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>The cross product vector of the two vectors.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 CrossProduct(Vector3 vector1, Vector3 vector2)
		{
			return new Vector3(vector1.Y * vector2.Z - vector1.Z * vector2.Y, vector1.Z * vector2.X - vector1.X * vector2.Z, vector1.X * vector2.Y - vector1.Y * vector2.X);
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Computes the distance between two vectors.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>The distance between the two vectors.</returns>
        //-----------------------------------------------------------------------------------------

        public static float Distance(Vector3 vector1, Vector3 vector2)
		{
            Vector3 result = vector1 - vector2;

            return result.Magnitude;
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Computes the squared distance between two vectors.
        /// </summary>
        /// <remarks>This property can be usefull in performance critical sections.</remarks>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>The squared distance between the two vectors.</returns>
        //-----------------------------------------------------------------------------------------

		public static float DistanceSquared(Vector3 vector1, Vector3 vector2)
		{
            Vector3 result = vector1 - vector2;

            return result.MagnitudeSquared;
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Computes the angle between two vectors.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>The angle between the two vectors.</returns>
        //-----------------------------------------------------------------------------------------

        public static float ComputeAngle(Vector3 vector1, Vector3 vector2)
		{
            // Compute the dividor of the operation
            float dividor = (vector1.Magnitude * vector2.Magnitude);

            // Check for divide by zero error
            if(dividor == 0)
            {
                throw new ArgumentException("Cannot compute angle of a zero vector.");
            }

            // Compute the angle between the two vectors
            return (float) System.Math.Acos((vector1 * vector2) / dividor);
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Negates the specified vector.
        /// </summary>
        /// <param name="vector">The vector to negate.</param>
        /// <returns>Negated copy of the vector.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 Negate(Vector3 vector)
        {
            return new Vector3(-vector.X, -vector.Y, -vector.Z);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the negate operator.
        /// </summary>
        /// <param name="vector">The vector to negate.</param>
        /// <returns>Negated copy of the vector.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 operator -(Vector3 vector)
		{
			return Negate(vector);
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Adds the two specified vectors.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>Result of the addition.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 Add(Vector3 vector1, Vector3 vector2)
        {
            return new Vector3(vector1.X + vector2.X, vector1.Y + vector2.Y, vector1.Z + vector2.Z);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the add operator.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>Result of the addition.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 operator +(Vector3 vector1, Vector3 vector2)
		{
			return Add(vector1, vector2);
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Subtracts the two specified vectors.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>Result of the substraction.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 Subtract(Vector3 vector1, Vector3 vector2)
        {
            return new Vector3(vector1.X - vector2.X, vector1.Y - vector2.Y, vector1.Z - vector2.Z);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the substract operator.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>Result of the substraction.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 operator -(Vector3 vector1, Vector3 vector2)
        {
            return Subtract(vector1, vector2);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Multiplies the specified vector with a scalar.
        /// </summary>
        /// <param name="vector">The vector.</param>
        /// <param name="scalar">The scalar.</param>
        /// <returns>Result vector of the scalar multiplication.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 Multiply(Vector3 vector, float scalar)
        {
            return new Vector3(vector.X * scalar, vector.Y * scalar, vector.Z * scalar);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the scalar multiplication operator.
        /// </summary>
        /// <param name="vector">The vector.</param>
        /// <param name="scalar">The scalar.</param>
        /// <returns>Result vector of the scalar multiplication.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 operator *(Vector3 vector, float scalar)
		{
			return Multiply(vector, scalar);
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Divides the specified vector with a scalar.
        /// </summary>
        /// <param name="vector">The vector.</param>
        /// <param name="scalar">The scalar.</param>
        /// <returns>Result vector of the scalar division.</returns>
        //-----------------------------------------------------------------------------------------

        public static Vector3 Divide(Vector3 vector, float scalar)
        {
            //Check for divide by zero
            if(scalar == 0.0f)
            {
                throw new DivideByZeroException();
            }

            return new Vector3(vector.X / scalar, vector.Y / scalar, vector.Z / scalar);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the scalar divide operator.
        /// </summary>
        /// <param name="vector">The vector.</param>
        /// <param name="scalar">The scalar.</param>
        /// <returns>Result vector of the scalar division.</returns>
        //-----------------------------------------------------------------------------------------
        
        public static Vector3 operator /(Vector3 vector, float scalar)
        {
            return Divide(vector, scalar);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Computes the dot product between two vectors.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>Result of the dot product.</returns>
        //-----------------------------------------------------------------------------------------

        public static float DotProduct(Vector3 vector1, Vector3 vector2)
        {
            return vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the dot product operator.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>Result of the dot product.</returns>
        //-----------------------------------------------------------------------------------------
        
        public static float operator *(Vector3 vector1, Vector3 vector2)
        {
            return DotProduct(vector1, vector2);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <param name="obj">Another object to compare to.</param>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent 
        /// the same value; otherwise, false.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public override bool Equals(object obj)
		{
			return obj is Vector3 ? Equals((Vector3) obj) : false;
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; 
        /// otherwise, false.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public bool Equals(Vector3 other)
		{
			return this.X == other.X && this.Y == other.Y && this.Z == other.Z;
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the equal operator.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>
        /// true if the two vectors are equal; otherwise, false.
        /// </returns>
        //-----------------------------------------------------------------------------------------
        
        public static bool operator ==(Vector3 vector1, Vector3 vector2)
        {
            return vector1.Equals(vector2);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the unequal operator.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>
        /// true if the two vectors are unequal; otherwise, false.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public static bool operator !=(Vector3 vector1, Vector3 vector2)
        {
            return !vector1.Equals(vector2);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public override int GetHashCode()
		{
			return this.X.GetHashCode() ^ this.Y.GetHashCode() ^ this.Z.GetHashCode();
		}


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Returns the fully qualified type name of this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> containing a desciption of the vector.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public override string ToString()
		{
            return string.Format(CultureInfo.InvariantCulture, "[{0} {1} {2}]", this.X.ToString("0.####"), this.Y.ToString("0.####"), this.Z.ToString("0.####"));
		}
	}
}
